export function createElement(vnode) {
  let { tag, data, children, text } = vnode
  // 如果tag 存在说明是标签
  if (tag) {
    vnode.el = document.createElement(tag)
    patchProps(vnode.el, {}, data)
    children && children.forEach(child => {
      vnode.el.appendChild(createElement(child))
    });
  } else {
    vnode.el = document.createTextNode(text)
  }
  return vnode.el
}

export function patchProps(el, oldData = {}, data = {}) {
  // -----这里是针对diff算法里的patchProps的使用，如果旧的节点又属性新节点没有，则直接删掉-----
  Object.keys(oldData).forEach(key => {
    // style在ast语法树进行解析template的时候已经被转化成了对象的形式，需要单独处理
    if (key === 'style') {
      Object.keys(oldData[key]).forEach(styleName => {
        if (!data[key][styleName]) {
          el.style[styleName] = ""
        }
      })
    }
    else {
      if (!data[key]) {
        el.removeAttribute(key)
      }
    }
  })

  Object.keys(data).forEach(key => {
    // 对样式做特殊处理，其余的属性都是基本类型，样式在前面生成render函数前，转化为ast语法树时已经变为对象
    if (key === "style") {
      Object.keys(data[key]).forEach(styleName => {
        el.style[styleName] = data[key][styleName]
      })
    } else {
      el.setAttribute(key, data[key])
    }
  })
}

export function patch(oldVnode, vnode) {
  // 如果oldVnode是真实的节点, el.nodeType有值 > 0
  // console.log(oldVnode.nodeType)
  let isRealNode = oldVnode.nodeType
  // 如果是真实的节点，说明是第一次渲染节点，直接挂载到$app上即可，不需要进行diff对比
  if (isRealNode) {
    let oldnode = oldVnode
    let parentNode = oldnode.parentNode
    debugger
    let newNode = createElement(vnode)
    // 在其oldnode之前插入 newNode 的节点。如果为 null，newNode 将被插入到节点的子节点列表末尾
    parentNode.insertBefore(newNode, oldnode)
    parentNode.removeChild(oldnode)
    return newNode
  } else {
    // 如果走入此分支，则进行diff算法比对，此时一定不是第一次渲染，返回真实dom
    return patchVnode(oldVnode, vnode)
  }
}

export function patchVnode(oldVnode, vnode) {
  if (!isSameVnode(oldVnode, vnode)) {
    // 如果根（父）节点都不相等，直接替换
    let parentNode = oldVnode.el.parentNode
    let newEl = createElement(vnode)
    // 旧节点变新的文本也会在这里呗删除
    parentNode.replaceChild(newEl, oldVnode.el)
    return newEl
  }

  // 注意，旧节点的每一个vnode都是使用createElement递归创建了dom，此时vnode.el已经挂了真实的dom
  // 如果节点相同，复用el
  let el = vnode.el = oldVnode.el

  // 文本
  if (!oldVnode.tag) {
    if (oldVnode.text !== vnode.text) {
      el.textContent = vnode.text
    }
    return el
  }
  // 标签，进行比较属性
  patchProps(el, oldVnode.data, vnode.data)

  let oldChildren = oldVnode.children || []
  let newChildren = vnode.children || []

  // 比较孩子
  if (oldChildren.length > 0 && newChildren.length > 0) {
    // 如果新旧都有孩子，diff比较
    updateChildren(el, oldChildren, newChildren)
  } else if (oldChildren.length > 0) {
    // 此分支新的虚拟节点没有孩子，直接删了全部，不可以用remove以为可能为组件
    el.innerHTML = ""
  } else if (newChildren.length > 0) {
    // 此分支旧的分支没有孩子
    mountChildren(el, newChildren)
  }
  return el
}

export function updateChildren(el, oldChildren, newChildren) {
  // 详细vue源码手写课，见diff算法视频，16 17集

  // 双指针比较
  let oldStartIndex = 0
  let newStartIndex = 0
  let oldStartVnode = oldChildren[oldStartIndex]
  let newStartVnode = newChildren[newStartIndex]

  let oldEndIndex = oldChildren.length - 1
  let newEndIndex = newChildren.length - 1
  let oldEndVnode = oldChildren[oldEndIndex]
  let newEndVnode = newChildren[newEndIndex]
  let map = makeMap(oldChildren)

  // 只要新旧节点有任意一方出现开头指针index>结束指针index就会停止循环，此时正常情况开始的指针一定在结束指针的后一位
  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    if (!oldStartVnode) {
      // 可能在最后的else分支内乱序把
      oldStartVnode = oldChildren[++oldStartIndex]
      continue
    } else if (!oldEndVnode) {
      oldEndVnode = oldChildren[--oldEndIndex]
      continue
    }
    if (isSameVnode(oldStartVnode, newStartVnode)) {
      // 头头比较，如果相等头头都后移
      patchVnode(oldStartVnode, newStartVnode)
      oldStartVnode = oldChildren[++oldStartIndex]
      newStartVnode = newChildren[++newStartIndex]
    } else if (isSameVnode(oldEndVnode, newEndVnode)) {
      // 尾尾比较，如果相等尾尾都前移
      patchVnode(oldEndVnode, newEndVnode)
      oldEndVnode = oldChildren[--oldEndIndex]
      newEndVnode = newChildren[--newEndIndex]
    } else if (isSameVnode(oldStartVnode, newEndVnode)) {
      // 头尾比较，如果相等头后移，尾前移
      patchVnode(oldStartVnode, newEndVnode)
      // 把老的头插到尾的后面
      // insertBefore会移动已有的元素到某个位置
      el.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling)
      oldStartVnode = oldChildren[++oldStartIndex]
      newEndVnode = newChildren[--newEndIndex]
    } else if (isSameVnode(oldEndVnode, newStartVnode)) {
      // 尾头比较，如果相等尾前移，头后移
      // insertBefore会移动已有的元素到某个位置
      el.insertBefore(oldEndVnode.el, oldStartVnode.el)
      oldEndVnode = oldChildren[--oldEndIndex]
      newStartVnode = newChildren[++newStartIndex]
    } else {
      // 此时为乱序, 拿到索引map
      // 为了最大程度复用旧的dom，直接取新节点key再map里取值，如果没有说明此节点一定要更新，直接更新即可
      let moveIndex = map[newStartVnode.key]
      if (moveIndex) {
        // 如果取到了,说明旧的dom也有这个key,再比较tag是否一样,如果一样直接复用,采用insertBefore移动旧节点即可
        let moveVnode = oldChildren[moveIndex]
        if (isSameVnode(newStartVnode, moveVnode)) {
          patchVnode(moveVnode, newStartVnode)
          // 把匹配到的旧节点放在旧头指针指向的vnode前
          el.insertBefore(moveVnode.el, oldStartVnode.el)
          // 这里移动了真实的dom，对应的虚拟dom也要置空，这就是开头头尾指针可能取到空的原因，要跳过
          oldChildren[moveIndex] = undefined
        } else {
          // 如果不是同一个元素即使key一样也直接更新
          el.insertBefore(createElement(newStartVnode), oldStartVnode.el)
        }
      } else {
        // key不一样直接更新
        el.insertBefore(createElement(newStartVnode), oldStartVnode.el)
      }
      // 新头++
      newStartVnode = newChildren[++newStartIndex]
    }
  }

  // 此时，新节点开始指针在结束指针的前面或重叠，说明新节点有多的dom，直接把多的插入旧节点即可
  if (newEndIndex >= newStartIndex) {
    for (let i = newStartIndex; i <= newEndIndex; i++) {
      // 可能是向前追加，也可能是向后追加
      let anchor = oldChildren[oldStartIndex].el ? oldChildren[oldStartIndex].el : null
      // insertBefore第二个参数是null时直接追加都最后（也就是向后追加）
      el.insertBefore(createElement(newChildren[i]), anchor)
    }
  }

  // 此时，旧节点开始指针在结束指针的前面或重叠，说明旧节点有多的dom，直接把多的删除即可
  if (oldEndIndex >= oldStartIndex) {
    for (let i = oldStartIndex; i <= oldEndIndex; i++) {
      if (oldChildren[i]) {
        el.removeChild(oldChildren[i].el)
      }
    }
  }

  // 注意，在while循环的前4个分支里（头头比，尾尾比，以及头尾尾头比），都是为了优化，
  // 比如数据驱动节点更新，数组的push，pop，unshift，shift，reverse或混合等等
  // 最后一个是乱序，但是也使用旧vnode的索引表，生成map最大程度复用旧节点

  // 在patchVnode的递归和循环中，不停频繁的操作dom，但是这是直接操作，浏览器会自动优化，一起移动dom
  // 而不会多次重绘重排
}

// 生成索引表
export function makeMap(children) {
  let map = {}
  children.forEach((child, index) => {
    map[child.key] = index
  })
  return map
}

export function mountChildren(parent, children) {
  children.forEach(child => {
    parent.appendChild(createElement(child))
  })
}

export function isSameVnode(oldVnode, newVnode) {
  return oldVnode.key === newVnode.key && oldVnode.tag === newVnode.tag
}